﻿// -----------------------------------------------------------------------
// <copyright file="CurrentUser.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System.Data.Odbc;
using System.Linq;

namespace Centido.Core
{
    using System;
    using System.Collections.Generic;
    using System.Data;

    /// <summary>
    ///   The currently logged in user.
    /// </summary>
    public sealed class CurrentUser : IUser, IRefreshable
    {
        /// <summary>
        ///   The current domain.
        /// </summary>
        private readonly Domain _domain;

        /// <summary>
        ///   The name of the current user.
        /// </summary>
        private string _name;

        /// <summary>
        ///   The display name of the current user.
        /// </summary>
        private string _displayName;

        /// <summary>
        ///   The current user ability to change the schema.
        /// </summary>
        private bool? _canChangeSchema;

        /// <summary>
        ///   The current user ability to change security settings.
        /// </summary>
        private bool? _canChangeSecurity;

        /// <summary>
        ///   Initializes a new instance of the <see cref="Centido.Core.CurrentUser"/> class
        /// for the specified domain.
        /// </summary>
        /// <param name = "domain">The current domain.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="domain"/> is null.</exception>
        internal CurrentUser(Domain domain)
        {
            if (domain == null)
                throw new ArgumentNullException("domain");

            _domain = domain;
        }

        /// <summary>
        ///   Gets the current domain.
        /// </summary>
        public Domain Domain
        {
            get { return _domain; }
        }

        /// <summary>
        ///   Gets or sets the current user display name.
        /// </summary>
        /// <exception cref="System.Data.Common.DbException">When trying to set the peroperty equal to a too long value.</exception>
        /// <example>
        ///   The following example shows how to set the display name of the current user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Setting the current user display name
        ///             domain.CurrentUser.DisplayName = "The current user display name";
        /// 
        ///             Console.WriteLine("Current user: display name = {0}", domain.CurrentUser.DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string DisplayName
        {
            get { return _displayName ?? (_displayName = DbValue.GetString(
                ReadProperty(Db.Views.UserNameList.Col.DisplayName))); }
            set
            {
                if (value != null)
                    value = value.Trim();

                if (!string.IsNullOrEmpty(_displayName) && _displayName == value) return;

                WriteProperty(Db.Views.CurrentUser.Col.DisplayName, value, OdbcType.NVarChar);

                _displayName = value;
            }
        }

        /// <summary>
        ///   Gets the current user permission to change the schema.
        /// </summary>
        /// <value>True if the current user has the permission to change the schema; otherwise, false.</value>
        /// <example>
        ///   The following example show how to check the current user permission to change the schema.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             if (domain.CurrentUser.CanChangeSchema)
        ///             {
        ///                 domain.AddTable("my_table");
        /// 
        ///                 Console.WriteLine("The current user has the permission to change the schema");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanChangeSchema
        {
            get 
            { 
                return _canChangeSchema ?? (_canChangeSchema = DbValue.GetInteger(
                    ReadProperty(Db.Views.CurrentUser.Col.CanChangeSchema)) == 1).Value;
            }
        }

        /// <summary>
        ///   Gets the current user permission to change security settings.
        /// </summary>
        /// <value>True if the current user has the permission to change security settings; otherwise, false.</value>
        /// <example>
        ///    The following example show how to check the current user permission to change security settings.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             if (domain.CurrentUser.CanChangeSecurity)
        ///             {
        ///                 domain.AddRole("my_role");
        /// 
        ///                 Console.WriteLine("The current user has the permission to change security settings");
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanChangeSecurity
        {
            get 
            { 
                return _canChangeSecurity ?? (_canChangeSecurity = DbValue.GetInteger(
                    ReadProperty(Db.Views.CurrentUser.Col.CanChangeSecurity)) == 1).Value; 
            }
        }


        /// <summary>
        ///   Gets the current user table permissions.
        /// </summary>
        /// <remarks>The current user has to have the permission to change the security settings in order to read this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to read this property.</exception>
        /// <example>
        ///   The following example shows how to read the current user table permissions.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting all parmissions on the table to the current user
        ///             table.GrantAll();
        /// 
        ///             foreach(var perm in domain.CurrentUser.TablePermissions)
        ///             {
        ///                 Console.WriteLine("Table permissions = {0}", perm.DataAction);
        ///             }
        ///         }
        ///     }   
        /// </code>
        /// </example>
        public ICollection<TablePermission> TablePermissions
        {
            get
            {
                var resultList = new List<TablePermission>();

                var currentUserName = Name;

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2} where {3}=?",
                            Db.Tables.TableAcl.Col.TableId,
                            Db.Tables.TableAcl.Col.DataAction,
                            Domain.GetObjectFullName(Db.Tables.TableAcl.Name),
                            Db.Tables.TableAcl.Col.PrincipalName);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("principal_name",
                            OdbcType.NVarChar, Name));
                    },
                    reader =>
                    {
                        var tableId = reader.GetGuid(Db.Tables.TableAcl.Col.TableId)
                            .GetValueOrDefault();
                        var dataActionValue = reader.GetInteger(Db.Tables.TableAcl.Col.DataAction)
                            .GetValueOrDefault();

                        var dataAction = (DataAction)dataActionValue;

                        resultList.Add(new TablePermission(new Table(tableId, Domain), currentUserName, dataAction));
                    });

                return resultList;
            }
        }

        /// <summary>
        ///   Gets the current user table security access rights.
        /// </summary>
        /// <remarks>Each table permissions produces a number of more granular level
        /// security access rigths.</remarks>
        /// <example>
        ///   The following example shows how to get the current user security access rights.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating two users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Adding the current and the first user to the same unit
        ///             var unit = domain.AddUnit("my_unit");
        /// 
        ///             unit.AddUser(firstUser);
        ///             unit.AddUser(domain.CurrentUser);
        ///             
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the permission to select all rows to the current user
        ///             table.Grant(DataAction.Select);
        ///             //Granting the permission to update unit data to the current user
        ///             table.Grant(DataAction.Update, AccessLevel.Unit);
        /// 
        ///             foreach(var right in domain.CurrentUser.TableAccessRights)
        ///             {
        ///                 if (right is TableAccessRight) 
        ///                     Console.WriteLine("Access right: action = {0}, owner = {1}",
        ///                         (right as TableAccessRight).DataAction,
        ///                         (right as TableAccessRight).Owner.Name);
        ///                 else if (right is TableFullAccessRight) 
        ///                     Console.WriteLine("Access right: action = {0}, full access",
        ///                         (right as TableFullAccessRight).DataAction);
        ///             }
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<ITableAccessRight> TableAccessRights
        {
            get
            {
                var resultList = new List<ITableAccessRight>();

                // Reading full access rights
                resultList.AddRange(TableFullAccessRights.OfType<ITableAccessRight>());

                // Reading access rights
                resultList.AddRange(TableUnitAccessRights.OfType<ITableAccessRight>());

                // Reading own rows rights
                resultList.AddRange(TableOwnRowsAccessRights.OfType<ITableAccessRight>());

                return resultList;
            }
        }


        /// <summary>
        ///   Gets a list of the current user table full access rights.
        /// </summary>
        public ICollection<TableFullAccessRight> TableFullAccessRights
        {
            get
            {
                var resultList = new List<TableFullAccessRight>();

                var currentUserName = Name;

                // Reading full access rights
                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2}",
                            Db.Views.UserTableAclFullAccess.Col.TableId,
                            Db.Views.UserTableAclFullAccess.Col.DataAction,
                            Domain.GetObjectFullName(Db.Views.UserTableAclFullAccess.Name));
                    },
                    reader =>
                    {
                        var tableId = reader.GetGuid(Db.Views.UserTableAclFullAccess.Col.TableId)
                            .GetValueOrDefault();
                        var dataActionValue = reader.GetInteger(Db.Views.UserTableAclFullAccess.Col.DataAction)
                            .GetValueOrDefault();

                        var dataAction = (DataAction)dataActionValue;

                        resultList.Add(
                            new TableFullAccessRight(
                                new Table(tableId, Domain), new User(currentUserName, Domain), dataAction));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets a list of the current user own rows table access rights.
        /// </summary>
        public ICollection<TableOwnRowsAccessRight> TableOwnRowsAccessRights
        {
            get
            {
                var resultList = new List<TableOwnRowsAccessRight>();

                var currentUserName = Name;

                // Reading full access rights
                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1} from {2}",
                            Db.Views.UserTableAclOwnRows.Col.TableId,
                            Db.Views.UserTableAclOwnRows.Col.DataAction,
                            Domain.GetObjectFullName(Db.Views.UserTableAclOwnRows.Name));
                    },
                    reader =>
                    {
                        var tableId = reader.GetGuid(Db.Views.UserTableAclOwnRows.Col.TableId)
                            .GetValueOrDefault();
                        var dataActionValue = reader.GetInteger(Db.Views.UserTableAclOwnRows.Col.DataAction)
                            .GetValueOrDefault();

                        var dataAction = (DataAction)dataActionValue;

                        resultList.Add(
                            new TableOwnRowsAccessRight(
                                new Table(tableId, Domain), new User(currentUserName, Domain), dataAction));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets a list of the current user table unit-level access rights.
        /// </summary>
        public ICollection<TableUnitAccessRight> TableUnitAccessRights
        {
            get
            {
                var resultList = new List<TableUnitAccessRight>();

                var currentUserName = Name;

                // Reading unit-level access rights
                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0}, {1}, {2} from {3}",
                            Db.Views.UserTableAclUnitAccess.Col.TableId,
                            Db.Views.UserTableAclUnitAccess.Col.UnitId,
                            Db.Views.UserTableAclUnitAccess.Col.DataAction,
                            Domain.GetObjectFullName(Db.Views.UserTableAclUnitAccess.Name));
                    },
                    reader =>
                    {
                        var tableId = reader.GetGuid(Db.Views.UserTableAclUnitAccess.Col.TableId)
                            .GetValueOrDefault();
                        var unitId = reader.GetGuid(Db.Views.UserTableAclUnitAccess.Col.UnitId)
                            .GetValueOrDefault();
                        var dataAction = (DataAction)reader.GetInteger(Db.Views.UserTableAclUnitAccess.Col.DataAction)
                            .GetValueOrDefault();

                        resultList.Add(
                            new TableUnitAccessRight(
                                new Table(tableId, Domain),
                                new User(currentUserName, Domain),
                                unitId,
                                dataAction));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets the current user name.
        /// </summary>
        /// <example>
        ///   The following example shows how to get the current user name.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             Console.WriteLine("Current user: name = {0}", domain.CurrentUser.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public string Name
        {
            get
            {
                if (!string.IsNullOrEmpty(_name))
                    return _name;

                var userNameParam = Domain.DbAccess.CreateParameter("result", OdbcType.NVarChar, null);
                userNameParam.Size = 64;
                userNameParam.Direction = ParameterDirection.Output;

                Domain.DbAccess.ExecuteNonQuery(
                    cmd =>
                    {
                        cmd.CommandText = string.Format("{{call {0}(?)}}", 
                            Domain.GetObjectFullName(Db.Procedures.GetCurrentUser));

                        cmd.Parameters.Add(userNameParam);
                    });

                return (_name = DbValue.GetString(userNameParam.Value).ToLower());
            }
        }


        /// <summary>
        ///   Changes the current user password.
        /// </summary>
        /// <param name = "oldPassword">The old password.</param>
        /// <param name = "newPassword">The new password.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="oldPassword"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="newPassword"/> is null.</exception>
        /// <exception cref="System.Data.Common.DbException">Thrown when the old password is not a correct one.</exception>
        /// <example>
        ///   The following example shows how to change the current uer password.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Changing the current user password
        ///             domain.CurrentUser.ChangePassword("P@ssw0rd", "NewP@ssw0rd");
        /// 
        ///             //Logging in with the new password
        ///             var newDomain = domain.Logon(domain.CurrentUser.Name, "NewP@ssw0rd");
        /// 
        ///             if (newDomain != null)
        ///                 Console.WriteLine("The password was changed");
        ///         }
        ///     }
        /// </code>
        /// </example>
        public void ChangePassword(string oldPassword, string newPassword)
        {
            if (oldPassword == null)
                throw new ArgumentNullException("oldPassword");
            if (newPassword == null)
                throw new ArgumentNullException("newPassword");

            Domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format("{{call {0}(?,?)}}",
                        Domain.GetObjectFullName(Db.Procedures.ChangeCurrentUserPassword));

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_old_password", OdbcType.NVarChar, oldPassword));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("a_new_password", OdbcType.NVarChar, newPassword));
                });
        }

        /// <summary>
        ///   Reads the current user property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <returns>Returns the property value.</returns>
        private object ReadProperty(string propertyName)
        {
            object propertyValue = null;

            _domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select {0} from {1} where {2}=?",
                        propertyName,
                        Domain.GetObjectFullName(Db.Views.CurrentUser.Name),
                        Db.Tables.UserList.Col.UserName);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("user_name", OdbcType.NVarChar, Name));
                },
                result => propertyValue = result);

            return propertyValue;
        }

        /// <summary>
        ///   Writes the current user property.
        /// </summary>
        /// <param name = "propertyName">The property name.</param>
        /// <param name = "propertyValue">The property value.</param>
        /// <param name = "propertyType">The property value database type.</param>
        private void WriteProperty(string propertyName, object propertyValue, OdbcType propertyType)
        {
            var rowCount = _domain.DbAccess.ExecuteNonQuery(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "update {0} set {1}=?",
                        Domain.GetObjectFullName(Db.Views.CurrentUser.Name),
                        propertyName);

                    cmd.Parameters.Add(
                        Domain.DbAccess.CreateParameter("value", propertyType, DbValue.GetDbValue(propertyValue)));
                });

            if (rowCount < 1)
                throw new DbOperationException();
        }

        /// <summary>
        ///   Checks the current user ability to do the specified operation on the specified table.
        /// </summary>
        /// <param name="table">The specified table.</param>
        /// <param name="dataAction">The specified data action.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <returns>Returns true if the current user has the ability to perform the requested action; otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to check the current user ability to do the specified operation on the specified table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the select permission on the table to the current user
        ///             table.Grant(DataAction.Select);
        /// 
        ///             //Checking the current user ability to select from the table
        ///             if (domain.CurrentUser.CanDo(table, DataAction.Select))
        ///             {
        ///                 Console.WriteLine("The current user has the ability to select from the table");
        /// 
        ///                 Console.WriteLine("Table: row count = {0}", table.RowCount);
        ///             }            
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanDo(Table table, DataAction dataAction)
        {
            if (table == null)
                throw new ArgumentNullException("table");

            return CanDo(table.Id, dataAction);
        }

        /// <summary>
        ///   Checks the current user ability to do the specified operation on the specified table.
        /// </summary>
        /// <param name = "tableId">The id of the specified table.</param>
        /// <param name = "dataAction">The specified data action.</param>
        /// <returns>Returns true if the current user has the ability to do the specified operation on the specified table;
        /// otherwise, false.</returns>
        /// <example>
        ///     The following example shows how to check the current user ability to do the specified operation on the specified table.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the select permission on the table to the current user
        ///             table.Grant(DataAction.Select);
        /// 
        ///             //Checking the current user ability to select from the table
        ///             if (domain.CurrentUser.CanDo(table.Id, DataAction.Select))
        ///             {
        ///                 Console.WriteLine("The current user has the ability to select from the table");
        /// 
        ///                 Console.WriteLine("Table: row count = {0}", table.RowCount);
        ///             }            
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanDo(Guid tableId, DataAction dataAction)
        {
            var entryWasFound = false;

            // Searching in full access permissions
            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Views.UserTableAclFullAccess.Name),
                        Db.Views.UserTableAclFullAccess.Col.TableId,
                        Db.Views.UserTableAclFullAccess.Col.DataAction);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("access_type", OdbcType.Int, (int)dataAction));
                },
                result => entryWasFound = DbValue.GetInteger(result) == 1);

            if (entryWasFound)
                return entryWasFound;

            // Searching in unit-level access permissions
            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Views.UserTableAclUnitAccess.Name),
                        Db.Views.UserTableAclUnitAccess.Col.TableId,
                        Db.Views.UserTableAclUnitAccess.Col.DataAction);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("access_type", OdbcType.Int, (int)dataAction));
                },
                result => entryWasFound = DbValue.GetInteger(result) == 1);

            if (entryWasFound)
                return entryWasFound;

            // Searching in own rows access permissions
            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Views.UserTableAclOwnRows.Name),
                        Db.Views.UserTableAclOwnRows.Col.TableId,
                        Db.Views.UserTableAclOwnRows.Col.DataAction);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("access_type", OdbcType.Int, (int)dataAction));
                },
                result => entryWasFound = DbValue.GetInteger(result) == 1);

            return entryWasFound;
        }

        /// <summary>
        ///   Checks the current user ability to do the specified operation with table rows that have the specified owner.
        /// </summary>
        /// <param name = "table">The specified table.</param>
        /// <param name = "dataAction">The requested data action.</param>
        /// <param name = "rowOwner">The specified row owner.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="rowOwner"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="rowOwner"/> is an empty string.</exception>
        /// <returns>Returns true if the current user has the ability to do the specified operation with table rows
        /// that have the specified owner; otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to check the current user ability to do
        /// the specified operation with table rows that have the specified owner.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating another two users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Adding the current user and the first user in the same unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the select permission to the current user
        ///             table.Grant(DataAction.Select);
        /// 
        ///             //Granting the update unit data permission to the current user
        ///             table.Grant(DataAction.Update, AccessLevel.Unit);
        /// 
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 domain.CurrentUser.Name,
        ///                 domain.CurrentUser.CanDo(table, DataAction.Update, domain.CurrentUser.Name));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 firstUser.Name,
        ///                 domain.CurrentUser.CanDo(table, DataAction.Update, firstUser.Name));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 secondUser.Name,
        ///                 domain.CurrentUser.CanDo(table, DataAction.Update, secondUser.Name));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanDo(Table table, DataAction dataAction, string rowOwner)
        {
            if (table == null)
                throw new ArgumentNullException("table");
            
            return CanDo(table.Id, dataAction, rowOwner);
        }

        /// <summary>
        ///   Checks the current user ability to do the specified operation with table rows that have the specified owner.
        /// </summary>
        /// <param name="table">The specified table.</param>
        /// <param name="dataAction">The requested data action.</param>
        /// <param name="rowOwner">The specified row owner.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="table"/> is null.</exception>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="rowOwner"/> is null.</exception>
        /// <returns>Returns true if the current user has the ability to do the specified operation with table rows
        /// that have the specified owner; otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to check the current user ability to do
        /// the specified operation with table rows that have the specified owner.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating another two users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Adding the current user and the first user in the same unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the select permission to the current user
        ///             table.Grant(DataAction.Select);
        /// 
        ///             //Granting the update unit data permission to the current user
        ///             table.Grant(DataAction.Update, AccessLevel.Unit);
        /// 
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 domain.CurrentUser.Name,
        ///                 domain.CurrentUser.CanDo(table, DataAction.Update, domain.CurrentUser));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 firstUser.Name,
        ///                 domain.CurrentUser.CanDo(table, DataAction.Update, firstUser));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 secondUser.Name,
        ///                 domain.CurrentUser.CanDo(table, DataAction.Update, secondUser));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanDo(Table table, DataAction dataAction, IUser rowOwner)
        {
            return CanDo(table, dataAction, rowOwner.Name);
        }

        /// <summary>
        ///   Checks the current user ability to do the specified operation with table rows that have the specified owner.
        /// </summary>
        /// <param name="tableId">The id of the specified table.</param>
        /// <param name="dataAction">The requested data action.</param>
        /// <param name="rowOwner">The specified row owner.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="rowOwner"/> is null.</exception>
        /// <returns>Returns true if the current user has the ability to do the specified operation with table rows
        /// that have the specified owner; otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to check the current user ability to do
        /// the specified operation with table rows that have the specified owner.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating another two users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Adding the current user and the first user in the same unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the select permission to the current user
        ///             table.Grant(DataAction.Select);
        /// 
        ///             //Granting the update unit data permission to the current user
        ///             table.Grant(DataAction.Update, AccessLevel.Unit);
        /// 
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 domain.CurrentUser.Name,
        ///                 domain.CurrentUser.CanDo(table.Id, DataAction.Update, domain.CurrentUser));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 firstUser.Name,
        ///                 domain.CurrentUser.CanDo(table.Id, DataAction.Update, firstUser));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 secondUser.Name,
        ///                 domain.CurrentUser.CanDo(table.Id, DataAction.Update, secondUser));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanDo(Guid tableId, DataAction dataAction, IUser rowOwner)
        {
            return CanDo(tableId, dataAction, rowOwner.Name);
        }

        /// <summary>
        ///   Checks the current user ability to do the specified operation with table rows that have the specified owner.
        /// </summary>
        /// <param name = "tableId">The id of the specified table.</param>
        /// <param name = "dataAction">The requested data action.</param>
        /// <param name = "rowOwner">The specified row owner.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="rowOwner"/> is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the value of <paramref name="rowOwner"/> is an empty string.</exception>
        /// <returns>Returns true if the current user has the ability to do the specified operation with table rows
        /// that have the specified owner; otherwise, false.</returns>
        /// <example>
        ///   The following example shows how to check the current user ability to do
        /// the specified operation with table rows that have the specified owner.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Creating another two users
        ///             var firstUser = domain.AddUser("first_user", "P@ssw0rd");
        ///             var secondUser = domain.AddUser("second_user", "P@ssw0rd");
        /// 
        ///             //Adding the current user and the first user in the same unit
        ///             var unit = domain.AddUnit("my_unit");
        ///             unit.AddUser(domain.CurrentUser);
        ///             unit.AddUser(firstUser);
        /// 
        ///             //Creating a table
        ///             var table = domain.AddTable("my_table");
        /// 
        ///             //Granting the select permission to the current user
        ///             table.Grant(DataAction.Select);
        /// 
        ///             //Granting the update unit data permission to the current user
        ///             table.Grant(DataAction.Update, AccessLevel.Unit);
        /// 
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 domain.CurrentUser.Name,
        ///                 domain.CurrentUser.CanDo(table.Id, DataAction.Update, domain.CurrentUser.Name));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 firstUser.Name,
        ///                 domain.CurrentUser.CanDo(table.Id, DataAction.Update, firstUser.Name));
        ///             Console.WriteLine("The ability to update {0} rows = {1}",
        ///                 secondUser.Name,
        ///                 domain.CurrentUser.CanDo(table.Id, DataAction.Update, secondUser.Name));
        ///         }
        ///     }
        /// </code>
        /// </example>
        public bool CanDo(Guid tableId, DataAction dataAction, string rowOwner)
        {
            if (rowOwner == null)
                throw new ArgumentNullException("rowOwner");
            if (rowOwner.Trim().Length == 0)
                throw new ArgumentException("Row owner cannot be empty.");

            rowOwner = rowOwner.Trim().ToLower();

            // Searching for the specified owner user
            var owner = Domain.GetUser(rowOwner);

            // If the specified owner user was not found
            if (owner == null)
                return false;

            var entryWasFound = false;

            // Searching in full access permissions
            Domain.DbAccess.ExecuteScalar(
                cmd =>
                {
                    cmd.CommandText = string.Format(
                        "select 1 from {0} where {1}=? and {2}=?",
                        Domain.GetObjectFullName(Db.Views.UserTableAclFullAccess.Name),
                        Db.Views.UserTableAclFullAccess.Col.TableId,
                        Db.Views.UserTableAclFullAccess.Col.DataAction);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("access_type", OdbcType.Int, (int)dataAction));
                },
                result => entryWasFound = DbValue.GetInteger(result) == 1);

            if (entryWasFound)
                return entryWasFound;

            // If the current user is trying to access its own row
            if (rowOwner == Domain.CurrentUser.Name)
            {
                Domain.DbAccess.ExecuteScalar(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select 1 from {0} where {1}=? and {2}=?",
                            Domain.GetObjectFullName(Db.Views.UserTableAclOwnRows.Name),
                            Db.Views.UserTableAclOwnRows.Col.TableId,
                            Db.Views.UserTableAclOwnRows.Col.DataAction);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("access_type", OdbcType.Int, (int)dataAction));
                    },
                result => entryWasFound = DbValue.GetInteger(result) == 1);

                if (entryWasFound)
                    return entryWasFound;
            }

            // The owner units
            var ownerUnits = owner.Units.ToList();

            // Searching in unit-level access permissions
            Domain.DbAccess.ExecuteReader(
                cmd =>
                {
                    cmd.CommandText =
                        string.Format(
                            "select {0} from {1} where {2}=? and {3}=?",
                            Db.Views.UserTableAclUnitAccess.Col.UnitId,
                            Domain.GetObjectFullName(Db.Views.UserTableAclUnitAccess.Name),
                            Db.Views.UserTableAclUnitAccess.Col.TableId,
                            Db.Views.UserTableAclUnitAccess.Col.DataAction);

                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("table_id", OdbcType.Binary, tableId.ToByteArray()));
                    cmd.Parameters.Add(Domain.DbAccess.CreateParameter("access_type", OdbcType.Int, (int)dataAction));
                },
                reader =>
                {
                    // The unit id
                    var unitId = DbValue.GetGuid(reader[Db.Views.UserTableAclUnitAccess.Col.UnitId])
                        .GetValueOrDefault();

                    // If the current user has access to the unit of the row owner
                    if (ownerUnits.Any(u => u.Id == unitId))
                        entryWasFound = true;
                });

            return entryWasFound;
        }


        /// <summary>
        ///   Gets the current user business units.
        /// </summary>
        /// <example>
        ///   The following example shows how to get the current user units.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        ///             
        ///             //Adding the current user to two units
        ///             var firstUnit = domain.AddUnit("first_unit");
        ///             firstUnit.DisplayName = "The first unit";
        ///             firstUnit.AddUser(domain.CurrentUser);
        /// 
        ///             var secondUnit = domain.AddUnit("second_unit");
        ///             secondUnit.DisplayName = "The second unit";
        ///             secondUnit.AddUser(domain.CurrentUser);
        ///             
        ///             foreach(var unit in domain.Units)
        ///                 Console.WriteLine("The current user: unit = {0}", unit.DisplayName);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Unit> Units
        {
            get
            {
                var resultList = new List<Unit>();

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0},{1} from {2} where {3} in (select {4} from {5} where {6}=?)",
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UnitList.Col.UnitName,
                            Domain.GetObjectFullName(Db.Tables.UnitList.Name),
                            Db.Tables.UnitList.Col.UnitId,
                            Db.Tables.UserUnitList.Col.UnitId,
                            Domain.GetObjectFullName(Db.Tables.UserUnitList.Name),
                            Db.Tables.UserUnitList.Col.UserName);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("user_name", OdbcType.NVarChar, Name));
                    },
                    reader =>
                    {
                        // The unit id
                        var unitId = reader.GetGuid(Db.Tables.UnitList.Col.UnitId).GetValueOrDefault();

                        // The unit name
                        var unitName = reader.GetString(Db.Tables.UnitList.Col.UnitName);

                        resultList.Add(new Unit(unitId, unitName, Domain));
                    });

                return resultList;
            }
        }


        /// <summary>
        ///   Gets the current user roles.
        /// </summary>
        /// <remarks>The current user has to have the permission to change security settings in order to read this property.</remarks>
        /// <exception cref="System.Data.Common.DbException">Thrown when the current user has no permission to read this property.</exception>
        /// <example>
        ///   The following example shows how to get the roles that contain the current user.
        /// <code>
        ///     using System;
        ///     using Centido.Core;
        /// 
        ///     class Sample
        ///     {
        ///         static void Main()
        ///         {
        ///             var domainConfig = new DomainConfiguration()
        ///             {
        ///                 DbConnectionString = @"Driver={SQL Server};Server=.\SQLEXPRESS;Database=MyDb;Uid=admin;Pwd=P@ssw0rd;",
        ///                 DbProviderName = "MSSQLSERVER"
        ///             };
        ///         
        ///             var domain = Domain.Build(domainConfig);
        ///         
        ///             domain.DeleteAll();
        /// 
        ///             //Adding the current user to two role
        ///             domain.AddRole("first_role").AddUser(domain.CurrentUser);
        ///             domain.AddRole("second_role").AddUser(domain.CurrentUser);
        /// 
        ///             foreach(var role in domain.CurrentUser.Roles)
        ///                 Console.WriteLine("Current user: role = {0}", role.Name);
        ///         }
        ///     }
        /// </code>
        /// </example>
        public ICollection<Role> Roles
        {
            get
            {
                var resultList = new List<Role>();

                var currentUserName = Name;

                Domain.DbAccess.ExecuteReader(
                    cmd =>
                    {
                        cmd.CommandText = string.Format(
                            "select {0} from {1} where {2}=?",
                            Db.Tables.UserRolesList.Col.ContainerRoleName,
                            Domain.GetObjectFullName(Db.Tables.UserRolesList.Name),
                            Db.Tables.UserRolesList.Col.UserName);

                        cmd.Parameters.Add(Domain.DbAccess.CreateParameter("user_name", OdbcType.NVarChar, currentUserName));
                    },
                    reader => resultList.Add(new Role(reader.GetString(Db.Tables.UserRolesList.Col.ContainerRoleName), Domain)));

                return resultList;
            }
        }

        /// <summary>
        ///   Refreshes the properties of the current user.
        /// </summary>
        public void Refresh()
        {
            _name = null;
            _displayName = null;
            _canChangeSchema = null;
            _canChangeSecurity = null;
        }
    }
}